import os
import sys
import db
import json
import math
import traceback
from datetime import datetime, timedelta
from utils.format import d2id
from collections import defaultdict
import pandas as pd
from utils.format import d2id, id2d, round3
from settings import RULE_MOD
from tqdm import tqdm
from utils.mail_send import SendEmail
from jinja2 import Environment, FileSystemLoader
from constants import HOME_PATH
from controllers.trade_cal import TradeCalController
from errors import CheckTriggerError

def send_trigger_mail(subject, toaddr, today_records):
    msg = SendEmail(subject, fromaddr="744189132@qq.com", toaddr=toaddr)
    base_path = os.path.join(HOME_PATH, "static")
    env = Environment(loader=FileSystemLoader(base_path))
    template = env.get_template("strategy_trigger_temp.html")
    # html_txt = template.render(today_records=today_records)
    # msg.add_html(html_txt)
    msg.add_html(template.render(today_records=today_records))
    msg.send()

# def _prase_idx(ind, idx):
#     res = []
#     for i in idx:
#         if not str(i).startswith("L"):
#             res.append(eval(i))
#         else:
#             res.append(ind[int(1:)])
#     return res

def _check_data(val_lst:list):
    data_type = (int, float)
    check_data_type = lambda x: all([isinstance(i, data_type) for i in x])
    check_data_val = lambda x: not any([math.isnan(i) for i in x])
    return check_data_type and check_data_val


def _check_trigger(inds, rule):
    split_rule = rule.split("-")
    rule_mod, idx = split_rule[0], split_rule[1:]
    ind = [inds[int(i[1:])] if i.startswith("L") else (0-eval(i[1:]) if i.startswith("n") else eval(i)) for i in idx]
    if rule_mod == RULE_MOD.MIN:
        if len(ind[0]) < 3:
            return None
        today, yesterday, before_yesterday = list(map(round3, [ind[0][-1], ind[0][-2], ind[0][-3]]))
        if _check_data([today, yesterday, before_yesterday]):
            if yesterday <= today and yesterday <= before_yesterday:
                return today, yesterday, before_yesterday
    elif rule_mod == RULE_MOD.MAX:
        if len(ind[0]) < 3:
            return None
        today, yesterday, before_yesterday = list(map(round3, [ind[0][-1], ind[0][-2], ind[0][-3]]))
        if _check_data([today, yesterday, before_yesterday]):
            if yesterday >= today and yesterday >= before_yesterday:
                return today, yesterday, before_yesterday
    elif rule_mod == RULE_MOD.UCROSS:
        if len(ind[0]) < 2 or len(ind[1]) < 2:
            return None
        n1_today, n1_yesterday = list(map(round3, [ind[0][-1], ind[0][-2]]))
        n2_today, n2_yesterday = list(map(round3, [ind[1][-1], ind[1][-2]]))
        if _check_data([n1_today, n1_yesterday, n2_today, n2_yesterday]):
            if n1_today >= n2_today and n1_yesterday < n2_yesterday:
                return n1_today, n1_yesterday, n2_today, n2_yesterday
    elif rule_mod == RULE_MOD.DCROSS:
        if len(ind[0]) < 2 or len(ind[1]) < 2:
            return None
        n1_today, n1_yesterday = list(map(round3, [ind[0][-1], ind[0][-2]]))
        n2_today, n2_yesterday = list(map(round3, [ind[1][-1], ind[1][-2]]))
        if _check_data([n1_today, n1_yesterday, n2_today, n2_yesterday]):
            if n1_today <= n2_today and n1_yesterday > n2_yesterday:
                return n1_today, n1_yesterday, n2_today, n2_yesterday
    elif rule_mod == RULE_MOD.LT:
        if len(ind[0]) < 1:
            return None
        today, val = list(map(round3, [ind[0][-1], ind[1][-1] if isinstance(ind[1], list) else ind[1]]))
        if _check_data([today, val]):
            if today < val:
                return today, val
    elif rule_mod == RULE_MOD.GT:
        if len(ind[0]) < 1:
            return None
        today, val = list(map(round3, [ind[0][-1], ind[1][-1] if isinstance(ind[1], list) else ind[1]]))
        if _check_data([today, val]):
            if today > val:
                return today, val
    elif rule_mod == RULE_MOD.LE:
        if len(ind[0]) < 1:
            return None
        today, val = list(map(round3, [ind[0][-1], ind[1][-1] if isinstance(ind[1], list) else ind[1]]))
        if _check_data([today, val]):
            if today <= val:
                return today, val
    elif rule_mod == RULE_MOD.GE:
        if len(ind[0]) < 1:
            return None
        today, val = list(map(round3, [ind[0][-1], ind[1][-1] if isinstance(ind[1], list) else ind[1]]))
        if _check_data([today, val]):
            if today >= val:
                return today, val
    elif rule_mod.startswith(RULE_MOD.LTN):
        if len(ind[0]) < 1:
            return None
        n1, n2 = 0-int(rule_mod[-2]), 0-int(rule_mod[-1])
        val1 = ind[0] if n1 == 0 else ind[0][n1]
        val2 = ind[1] if n2 == 0 else ind[1][n2]
        val1, val2 = list(map(round3, [val1, val2]))
        if _check_data([val1, val2]):
            if val1 < val2:
                return val1, val2
    elif rule_mod.startswith(RULE_MOD.GTN):
        if len(ind[0]) < 1:
            return None
        n1, n2 = 0-int(rule_mod[-2]), 0-int(rule_mod[-1])
        val1 = ind[0] if n1 == 0 else ind[0][n1]
        val2 = ind[1] if n2 == 0 else ind[1][n2]
        val1, val2 = list(map(round3, [val1, val2]))
        if _check_data([val1, val2]):
            if val1 > val2:
                return val1, val2
    elif rule_mod.startswith(RULE_MOD.LEN):
        if len(ind[0]) < 1:
            return None
        n1, n2 = 0-int(rule_mod[-2]), 0-int(rule_mod[-1])
        val1 = ind[0] if n1 == 0 else ind[0][n1]
        val2 = ind[1] if n2 == 0 else ind[1][n2]
        val1, val2 = list(map(round3, [val1, val2]))
        if _check_data([val1, val2]):
            if val1 <= val2:
                return val1, val2
    elif rule_mod.startswith(RULE_MOD.GEN):
        if len(ind[0]) < 1:
            return None
        n1, n2 = 0-int(rule_mod[-2]), 0-int(rule_mod[-1])
        val1 = ind[0] if n1 == 0 else ind[0][n1]
        val2 = ind[1] if n2 == 0 else ind[1][n2]
        val1, val2 = list(map(round3, [val1, val2]))
        if _check_data([val1, val2]):
            if val1 >= val2:
                return val1, val2
    else:
        raise Exception("(%s)rule_mod设置错误" % rule_mod)
    return None


def _check_all_triggers(inds, rules):
    """检验所有规则是否都满足"""
    check_res = []
    for rule in rules:
        res = _check_trigger(inds, rule)
        if res:
            check_res.append(res)
        else:
            break
    if len(check_res) == len(rules):
        return check_res
    return None


def check_one_strategy_trigger(ts_code, strategy, cal_date_int, daily=None):
    condition = json.loads(strategy["condition"])
    if not daily:
        daily = db.DailyTB.get_daily(ts_code, cal_date_int=cal_date_int)
    cond_res = []
    for j in condition:
        inds = db.IndicatorTB.get_stock_indicator(
                ts_code, j["abbr"], daily=daily, **j["params"])
        rules = str(j["rules"]).split(',')
        check_cond_res = _check_all_triggers(inds, rules)
        if check_cond_res:
            cond_res.append({j["abbr"]:check_cond_res})
        else:
            break
    # 写入数据库
    if len(cond_res) == len(condition):
        return cond_res
    else:
        return None


def indicator_trigger(ts_code, next_trade_day, strategy_name=''):
    daily = db.DailyTB.get_daily(ts_code, cal_date_int=next_trade_day)
    indicator_strategys = db.StrategyTB.find(available=1)
    for i in indicator_strategys:
        if strategy_name:
            if i["name"] != strategy_name:
                continue
        try:
            cond_res = check_one_strategy_trigger(ts_code, i, next_trade_day, daily)
            # 写入数据库
            if cond_res:
                db.TriggerRecordTB.add(
                    strategy=i["name"],
                    ts_code=ts_code,
                    orient=i["orient"],
                    condition=str(cond_res),
                    trigger_date=next_trade_day
                )
        except Exception as e:
            print(traceback.format_exc())


class StockController(object):
    """ stock """

    def __init__(self, ts_code=None, user=None):
        self.ts_code = ts_code
        self.user = user

    @classmethod
    def get_stocks(cls):
        """使用datasource获取stock信息
        """
        # 选取可用的gp
        stocks = db.StockTB.get_stocks()
        return stocks[:100]

    def get_daily(self):
        """使用datasource获取日day trade info
        """
        # 选取可用的gp
        return db.DailyTB.get_daily(self.ts_code)

    @classmethod
    def search(cls, s):
        if not s:
            return cls.get_stocks()
        stock1 = db.StockTB.find(ts_code=("like", "%{}%".format(s)))
        stock2 = db.StockTB.find(name=("like", "%{}%".format(s)))
        return stock1+stock2

    def get_indicator(self, indicator):
        """使用datasource获取日day trade inf
        parameters:
            indicator: str
        """
        # 选取可用的gp
        ind_df = db.IndicatorTB.get_stock_indicator(self.ts_code, indicator)
        return ind_df

    @classmethod
    def run_all(cls, cal_date_int=None, strategy_name=''):
        stocks = db.StockTB.get_stocks()
        # stocks = db.StockTB.find()

        next_trade_day = db.TradeCalTB.next_trade_day(cal_date_int)
        db.TriggerRecordTB.rm(trigger_date=next_trade_day)

        for s in tqdm(stocks):
            indicator_trigger(s['ts_code'], next_trade_day, strategy_name)
        # for num, s in enumerate(stocks):
        #     indicator_trigger(s['ts_code'], next_trade_day)
        #     if num == 2:
        #         break
        return {}

    @classmethod
    def send_trigger_msg(cls, cal_date_int=None):
        next_trade_day = db.TradeCalTB.next_trade_day(cal_date_int)
        all_today_trigger = db.TriggerRecordTB.select().where(
                trigger_date=next_trade_day).order_by("orient").all()
        stocks = db.StockTB.find()
        ts_code_name_map = {i["ts_code"]: i["name"] for i in stocks}
        for i in all_today_trigger:
            i["name"] = ts_code_name_map[i["ts_code"]]
        users = db.UsersTB.find(available=1)
        for i in users:
            send_trigger_mail("策略触发记录", i["email"], all_today_trigger)
        return {}

    def get_stock_strategy(self, orient, cal_date_int=None):
        next_trade_day = db.TradeCalTB.next_trade_day(cal_date_int)
        strategys = db.TriggerRecordTB.find(
                orient=orient,
                ts_code=self.ts_code,
                trigger_date=next_trade_day
                )
        return strategys


